//
//  LBReactBridge.m
//  LBReact
//
//  Created by WuShiHai on 19/5/24.
//  Copyright (c) 2019 Lebbay. All rights reserved.
//

#import "LBReactBridge.h"

#import <pthread.h>

#import <LBFoundation/LBFoundation.h>
#import <LBUIKit/LBUIKit.h>
#import <LBUIKit/FDFullscreenPopGesture.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTModalHostViewManager.h>

#import "LBReactConfigurationHeader.h"
#import "RCTBridge+SourceCode.h"
#import "LBReactBridge+Util.h"

//App.configure['loadAppPage']() 通知
NSString *const LBJavaScriptDidLoadAppNotification = @"LBJavaScriptDidLoadAppNotification";
NSString *const LBJavaScriptDidLoadModuleNotification = @"LBJavaScriptDidLoadModuleNotification";

@interface LBReactBridge ()<RCTBridgeDelegate> {
    NSThread *_jsThread;
    dispatch_group_t _jsGroup;
    BOOL _isLoanMain;
    NSLock *_completeLock;
}

@property (nonatomic, assign, readwrite) BOOL willManualReloadBridge;

@property (nonatomic, assign, readwrite) LBReactBridgeMode mode;

@property (nonatomic, strong, readwrite) LBBridge *bridge;
@property (nonatomic, strong, readwrite) NSString *bundlePath;
@property (nonatomic, strong, readwrite) NSURL *bundleURL;
@property (nonatomic, assign, readwrite) LBReactBridgeRenderState renderState;

@property (nonatomic, strong) LBReactBridgeJSCallbackBlock callback;
@property (nonatomic, strong) LBReactBridgeJSCallbackBlock renderCallback;

@property (nonatomic, strong) NSMutableArray<LBReactBridgeRenderCompleteBlock> *completeBlocks;

@property (nonatomic, strong) NSMutableArray<LBBridge *> *bridges;

@end

@implementation LBReactBridge

+ (instancetype)manager{
    static LBReactBridge *_manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _manager = [[LBReactBridge alloc] init];
    });
    return _manager;
}

+ (void)runRunLoop
{
    @autoreleasepool {
        // copy thread name to pthread name
        pthread_setname_np([NSThread currentThread].name.UTF8String);
        
        // Set up a dummy runloop source to avoid spinning
        CFRunLoopSourceContext noSpinCtx = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
        CFRunLoopSourceRef noSpinSource = CFRunLoopSourceCreate(NULL, 0, &noSpinCtx);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), noSpinSource, kCFRunLoopDefaultMode);
        CFRelease(noSpinSource);
        
        // run the run loop
        while (kCFRunLoopRunStopped != CFRunLoopRunInMode(kCFRunLoopDefaultMode, ((NSDate *)[NSDate distantFuture]).timeIntervalSinceReferenceDate, NO)) {
            RCTAssert(NO, @"not reached assertion"); // runloop spun. that's bad.
        }
    }
}

- (instancetype)init {
    if (self = [super init]) {
        _mode = LBReactBridgeMultiMode;
        _jsThread = [[NSThread alloc] initWithTarget:[self class]
                                            selector:@selector(runRunLoop)
                                              object:nil];
        _jsThread.name = @"com.lebbay.react.bridge";
        _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
#if RCT_DEBUG
        _jsThread.stackSize *= 2;
#endif
        [_jsThread start];
        
        _jsGroup = dispatch_group_create();
        _completeLock = [[NSLock alloc] init];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(javaScriptDidLoad:)
                                                     name:RCTJavaScriptDidLoadNotification
                                                   object:nil];
//        [[NSNotificationCenter defaultCenter] addObserver:self
//                                                 selector:@selector(javaScriptDidLoadModule:)
//                                                     name:LBJavaScriptDidLoadModuleNotification
//                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(javaScriptDidFailToLoad)
                                                     name:RCTJavaScriptDidFailToLoadNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(bridgeWillReload)
                                                     name:RCTBridgeWillReloadNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(bridgeWillStartLoading:)
                                                     name:RCTJavaScriptWillStartLoadingNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(bridgeWillStartExecuting:)
                                                     name:RCTJavaScriptWillStartExecutingNotification
                                                   object:nil];
        
    }
    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)javaScriptDidLoad:(NSNotification *)notification {
    [self javaScriptDidLoadModule:notification];
    [self configureHostManager];
}

- (void)javaScriptDidLoadModule:(NSNotification *)notification {
    if (_isLoanMain) {
        _renderState = LBReactBridgeRenderStateSuccess;
    }
    LBBridge *bridge = notification.object;
    if ([bridge isKindOfClass:[LBBridge class]]
        && bridge.state == LBBridgeUsedStateInit) {
        bridge.state = LBBridgeUsedStateFree;
    }
    [self runCompleteCallback:YES];
    dispatch_group_leave(_jsGroup);
}


- (void)javaScriptDidFailToLoad {
    if (_isLoanMain) {
        _renderState = LBReactBridgeRenderStateFail;
    }
    [self runCompleteCallback:NO];
    dispatch_group_leave(_jsGroup);
}

- (void)bridgeWillReload {
    
    //手动加载不需要进行入栈操作
    if (!self.willManualReloadBridge) {
        dispatch_group_enter(_jsGroup);
    }
    self.willManualReloadBridge = false;
    
    //当前两种状态时，说明是手动调用方法渲染的，不是重新加载
    if (self.renderState == LBReactBridgeRenderStateUnKown
        || self.renderState == LBReactBridgeRenderStateRendering) {
        return;
    }
    [_completeLock lock];
    [self.completeBlocks insertObject:^(BOOL i, NSError *error, BOOL j){}
                              atIndex:0];
    [_completeLock unlock];
    _isLoanMain = YES;
}

- (void)bridgeWillStartLoading:(NSNotification *)notification {

}

- (void)bridgeWillStartExecuting:(NSNotification *)notification {
    
}

- (void)runCompleteCallback:(BOOL)isSuccess {
    LBReactBridgeRenderCompleteBlock complete = [self.completeBlocks firstObject];
    LBBlockSafeRun(complete, isSuccess, !isSuccess ? LBReactBridgeRenderError(Loading) : nil, NO);
    if (complete) {
        [_completeLock lock];
        [self.completeBlocks removeObject:complete];
        [_completeLock unlock];
    }
}

- (void)registerJSCallback:(LBReactBridgeJSCallbackBlock)callback {
    __weak __typeof(&*self) weakSelf = self;
    self.callback = ^{
        weakSelf.renderState = LBReactBridgeRenderStateJSCallback;
        [[NSNotificationCenter defaultCenter] postNotificationName:LBJavaScriptDidLoadAppNotification
                                                            object:nil];
        if (callback) {
            callback();
        }
    };
}

- (void)registerJSRenderCallback:(LBReactBridgeJSCallbackBlock)renderCallback {
    self.renderCallback = renderCallback;
}

- (void)render:(LBReactBridgeMode)mode
    bundlePath:(NSString *)bundlePath
    bundleName:(NSString *)bundleName
 launchOptions:(NSDictionary *)launchOptions
      complete:(LBReactBridgeRenderCompleteBlock)complete {
    
    NSAssert([NSThread currentThread].isMainThread, @"请在主线程调用render:bundlePath:bundleName:launchOptions:complete");
    
    _mode = mode;
    _bundlePath = bundlePath;
    _renderState = LBReactBridgeRenderStateRendering;
    _isLoanMain = YES;
    
    //是否加载服务器的Bundle
    NSURL *bundleURL = nil;
    if (mode == LBReactBridgeServerMode) {
        NSString *bundleRoot = bundlePath ? [bundlePath stringByAppendingPathComponent:@"index.app"] : @"index.app";
        bundleURL = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:bundleRoot
                                                                   fallbackResource:nil];
    } else {
//        if (bundlePath.length > 0) {
//            _bundlePath = [LBReactBridge executePathFor:bundlePath];
//            if (![LBReactBridge removeFolder:_bundlePath]
//                || ![LBReactBridge inspectFolderPath:_bundlePath]
//                || ![LBReactBridge copyFolder:bundlePath
//                                           to:_bundlePath]
//                || ![LBReactBridge copyAssetFolder:bundlePath
//                                                to:_bundlePath]) {
//                LBBlockSafeRun(complete, NO, LBReactBridgeRenderError(OperationFolder));
//                return;
//            }
//        } else {
//            NSString *bundlePath = [LBReactBridge executePathForResource:bundleName?:@"index.app"
//                                                           withExtension:@"jsbundle"];
//            _bundlePath = [bundlePath stringByDeletingLastPathComponent];
//            NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:[bundlePath lastPathComponent]];
//
//            if (![LBReactBridge removeFolder:_bundlePath]
//                || ![LBReactBridge inspectFolderPath:_bundlePath]
//                || ![LBReactBridge copyFile:filePath
//                                         to:bundlePath]
//                || ![LBReactBridge copyAssetFolder:[[NSBundle mainBundle] resourcePath]
//                                                to:_bundlePath]) {
//                LBBlockSafeRun(complete, NO, LBReactBridgeRenderError(OperationFolder));
//                return;
//            }
//        }
//        NSString *localPathOfBundle = [LBReactBridge pathOfJSBundleIn:_bundlePath];
//        bundleURL = [NSURL fileURLWithPath:localPathOfBundle];
        
        if (bundlePath.length > 0) {
            NSString *localPathOfBundle = [LBReactBridge pathOfJSBundleIn:bundlePath];
            if (localPathOfBundle) {
                bundleURL = [NSURL fileURLWithPath:localPathOfBundle];
            } else {
                LBBlockSafeRun(complete, NO, LBReactBridgeRenderError(BundleNotFound), NO);
                return;
            }
        } else {
            bundleURL = [[NSBundle mainBundle] URLForResource:bundleName?:@"index.app"
                                                withExtension:@"jsbundle"];
        }
    }
    
    self.bundleURL = bundleURL;
    [self ensureOnJavaScriptThread:^{
        dispatch_group_wait(_jsGroup, DISPATCH_TIME_FOREVER);
        dispatch_group_enter(_jsGroup);
        [self addComplete:complete];
//        if (_bridge) {
//            self.willManualReloadBridge = YES;
//            [_bridge reload];
//        } else {
//            _bridge = [[LBBridge alloc] initWithDelegate:self
//                                           launchOptions:launchOptions];
//        }
        [_bridge invalidate];
        _bridge = [[LBBridge alloc] initWithDelegate:self
                                       launchOptions:launchOptions];
    }];
}

- (void)renderBackupBridge:(LBReactBridgeRenderCompleteBlock)complete {
    _isLoanMain = YES;
    [self ensureOnJavaScriptThread:^{
        dispatch_group_wait(_jsGroup, DISPATCH_TIME_FOREVER);
        dispatch_group_enter(_jsGroup);
        [self addComplete:complete];
        [self.bridges addObject:[[LBBridge alloc] initWithDelegate:self
                                                     launchOptions:self.bridge.launchOptions]];
    }];
}

- (void)render:(LBReactBridgeMode)mode
    bundlePath:(NSString *)bundlePath
 launchOptions:(NSDictionary *)launchOptions
      complete:(LBReactBridgeRenderCompleteBlock)complete {
    [self render:mode
      bundlePath:bundlePath
      bundleName:nil
   launchOptions:launchOptions
        complete:complete];
}

- (void)executeSourceCodeIn:(LBBridge *)bridge
                 folderPath:(NSString *)folderPath
                     module:(NSString *)module
                      index:(NSUInteger)index
                   complete:(LBReactBridgeRenderCompleteBlock)complete {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSError *error = nil;
        NSData *sourceCode = [NSData dataWithContentsOfFile:[LBReactBridge pathOfJSBundleIn:folderPath]
                                                    options:NSDataReadingMappedIfSafe
                                                      error:&error];
//        if (error) {
//            LBBlockSafeRun(complete, NO, error);
//        } else {
//            [self executeSourceCodeIn:bridge
//                           sourceCode:sourceCode
//                               module:module
//                                index:index
//                           folderPath:folderPath
//                             complete:complete];
//        }
        
        if ([LBReactBridge copyAssetFolder:[folderPath stringByAppendingPathComponent:module]
                                        to:_bundlePath]) {

        } else {
            LBBlockSafeRun(complete, NO, LBReactBridgeRenderError(OperationFolder), NO);
        }
    });
}

- (void)executeSourceCodeIn:(LBBridge *)bridge
                 sourceCode:(NSData *)sourceCode
                     module:(NSString *)module
                      index:(NSUInteger)index
                 folderPath:(NSString *)folderPath
                   complete:(LBReactBridgeRenderCompleteBlock)complete {
    LBBridgeModuleInfo *moduleInfo = [[LBBridgeModuleInfo alloc] init];
    moduleInfo.name = module;
    moduleInfo.index = index;
    moduleInfo.folderPath = [folderPath stringByAppendingPathComponent:module];
    [self executeSourceCodeIn:bridge
                   sourceCode:sourceCode
                  moduleInfos:@[moduleInfo]
                       module:module
                     complete:complete];
}

- (void)executeSourceCodeIn:(LBBridge *)bridge
                 sourceCode:(NSData *)sourceCode
                moduleInfos:(NSArray<LBBridgeModuleInfo *> *)moduleInfos
                     module:(NSString *)module
                   complete:(LBReactBridgeRenderCompleteBlock)complete {
    self->_isLoanMain = NO;
    [self ensureOnJavaScriptThread:^{
        dispatch_group_wait(_jsGroup, DISPATCH_TIME_FOREVER);
        dispatch_group_enter(_jsGroup);
        [self addComplete:complete];
        [moduleInfos enumerateObjectsUsingBlock:^(LBBridgeModuleInfo * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [bridge addModule:obj];
        }];
        [bridge executeSourceCode:sourceCode
                           module:module];            
    }];
}


- (void)ensureOnJavaScriptThread:(dispatch_block_t)block {
    RCTAssert(_jsThread, @"This method must not be called before the JS thread is created");
    
    // This does not use _jsMessageThread because it may be called early before the runloop reference is captured
    // and _jsMessageThread is valid. _jsMessageThread also doesn't allow us to shortcut the dispatch if we're
    // already on the correct thread.
    
    if ([NSThread currentThread] == _jsThread) {
        [self _try:block];
    } else {
        [self performSelector:@selector(_try:)
                     onThread:_jsThread
                   withObject:block
                waitUntilDone:NO];
    }
}

- (void)_try:(dispatch_block_t)block {
    if (block) {
        block();
    }
}

- (void)configureHostManager {
    dispatch_async(dispatch_get_main_queue(), ^{
        RCTModalHostViewManager *hostManager = [self.bridge moduleForName:@"ModalHostViewManager"];
        hostManager.presentationBlock = ^(UIViewController *reactViewController, UIViewController *viewController, BOOL animated, dispatch_block_t completionBlock) {
            viewController.fd_prefersNavigationBarHidden = YES;
            LBNavigationController *navigationController = [[LBNavigationController alloc] initWithRootViewController:viewController];
            navigationController.modalPresentationStyle = UIModalPresentationOverFullScreen;
            [[UIApplication displayViewController] presentViewController:navigationController
                                                                animated:animated
                                                              completion:completionBlock];
        };
        hostManager.dismissalBlock = ^(UIViewController *reactViewController, UIViewController *viewController, BOOL animated, dispatch_block_t completionBlock) {
            [viewController.presentingViewController  dismissViewControllerAnimated:animated
                                                                         completion:completionBlock];
        };
    });
}

#pragma mark - Router
- (LBBridge *)bridgeWithComponent:(NSString *)component {
    __block LBBridge *bridge = nil;
    
    NSString *module = [self moduleWith:component];
    
    NSInteger index = [_bridge indexForModule:module];
    //先从初始化的bridge中取路径
    if (index >= 0 && [_bridge isContainCompnent:component]) {
        bridge = _bridge;
    }
    
    [self.bridges enumerateObjectsUsingBlock:^(LBBridge * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSInteger index = [obj indexForModule:module];
        if (index >= 0 && [obj isContainCompnent:component] && (!bridge || [bridge indexForModule:module] < index)) {
            bridge = obj;
        }
    }];
    return bridge;
}

- (NSString *)moduleWith:(NSString *)component {
    return [[component componentsSeparatedByString:@"/"] firstObject];
}

- (void)fetchMainBrdige:(LBReactBridgeRenderMainBlock)complete {
    [self renderBackupBridge:^(BOOL isSuccess, NSError *error, BOOL isUpdate) {
        LBBridge *bridge = [self freeBridge];
        bridge.state = LBBridgeUsedStateWillIn;
        LBBlockSafeRun(complete, bridge, error);
    }];
}

- (void)increase:(LBBridge *)bridge {
    [bridge increase];
}

- (void)decrease:(LBBridge *)bridge {
    [bridge decrease];
    //启动渲染的bridge一直保留
    if (bridge != _bridge && bridge.usedCount == 0) {
        [bridge invalidate];
        [self.bridges removeObject:bridge];
    }
}

#pragma mark - RCTBridgeDelegate
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
    return self.bundleURL;
}

#pragma mark - Private
- (void)addComplete:(LBReactBridgeRenderCompleteBlock)complete {
    [_completeLock lock];
    if (complete) {
        [self.completeBlocks addObject:complete];
    } else {
        [self.completeBlocks addObject:^(BOOL i, NSError *e, BOOL j){}];
    }
    [_completeLock unlock];
}
- (LBBridge *)freeBridge {
    return [[[self.bridges copy] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(LBBridge *evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
        return evaluatedObject.state == LBBridgeUsedStateFree;
    }]] firstObject];
}

- (NSString *)keyFrom:(NSObject *)object {
    return [NSString stringWithFormat:@"%p", object];
}

#pragma mark - Init
- (NSMutableArray<LBReactBridgeRenderCompleteBlock> *)completeBlocks {
    if (_completeBlocks) {
        return _completeBlocks;
    }
    _completeBlocks = [@[] mutableCopy];
    return _completeBlocks;
}

- (NSMutableArray<LBBridge *> *)bridges {
    if (_bridges) {
        return _bridges;
    }
    _bridges = [@[] mutableCopy];
    return _bridges;
}

@end
